// Copyright (c) Microsoft Corporation.  All rights reserved.
#include "Common.h"

CSSIcon::CSSIcon() :
    m_pData(NULL),
    m_hBitmap(NULL),
    m_nWidth(0),
    m_nHeight(0)
{
}

CSSIcon::~CSSIcon()
{
    _ClearBitmap();
    _ClearData();
}

VOID CSSIcon::_ClearData()
{
    if (m_pData)
    {
        delete [] m_pData;
        m_pData = NULL;
    }
}

VOID CSSIcon::_ClearBitmap()
{
    if (m_hBitmap)
    {
        DeleteObject(m_hBitmap);
        m_hBitmap = NULL;
    }
}

HRESULT CSSIcon::SetData(const BYTE* pData, size_t cbData)
{
    HRESULT hr = S_OK;

    _ClearData();
    _ClearBitmap();
    
    if (pData && cbData)
    {
        BITMAPINFOHEADER *bmih;
        
        bmih = (BITMAPINFOHEADER *)(pData + sizeof(BITMAPFILEHEADER));
        
        if (bmih->biBitCount == 32)
        {
            m_nWidth = bmih->biWidth;
            m_nHeight = abs(bmih->biHeight);
            
            m_pData = new(std::nothrow) BYTE[cbData];
            
            if (m_pData)
            {
                memcpy(m_pData, pData, cbData);
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }
        else
        {
            // Per Microsoft Developer Network (MSDN), Windows SideShow Icon is 32 bpp
            hr = E_UNEXPECTED;
        }
    }
    else
    {
        // should hr = E_FAIL ?
    }
    
    return hr;
}

UINT CSSIcon::GetWidth()
{
    return m_nWidth;
}

UINT CSSIcon::GetHeight()
{
    return m_nHeight;
}

//
// need to do alpha blend for drawing SideShow icon.
// because SideShow icon is 32 bit and it may have an alpha value.
//
SIZE CSSIcon::Draw(HDC hdc, int x, int y)
{
    return AlphaBlend(hdc, x, y, 255);  // call member func AlphaBlend with bSrcAlpha=255.
}

SIZE CSSIcon::AlphaBlend(HDC hdc, int x, int y, BYTE bSrcAlpha)
{
    SIZE ret = { 0, 0 };

    if (m_nWidth == 0 || m_nHeight == 0)
    {
        return ret;
    }

#if 1
    //
    // alpha blend without calling GDI to avoid x64 problem.
    //
    
    BITMAPINFOHEADER *bmih;
    BYTE *bits;

    bmih = (BITMAPINFOHEADER *)(m_pData + sizeof(BITMAPFILEHEADER));
    bits = m_pData + ((BITMAPFILEHEADER *)m_pData)->bfOffBits;
    
    DWORD *pdwARGB;
    
    if (bmih->biHeight > 0)
    {
        // bottom-up
        pdwARGB = &((DWORD *)bits)[(bmih->biHeight - 1) * bmih->biWidth];
    }
    else
    {
        // top-down
        pdwARGB = (DWORD *)bits;
    }

    for(UINT _y = 0; _y < m_nHeight; _y++)
    {
        for(int _x = 0; _x < bmih->biWidth; _x++)
        {
            DWORD dwSrcARGB = pdwARGB[_x];
            DWORD a = (dwSrcARGB >> 24) & 0xFF; // per-pixel alpha value
            DWORD r = (dwSrcARGB >> 16) & 0xFF;
            DWORD g = (dwSrcARGB >> 8) & 0xFF;
            DWORD b = dwSrcARGB & 0xFF;
            
            if (a == 0)
            {
                // this means transparent, do thing
            }
            else
            {
                COLORREF dstRGB = GetPixel(hdc, x + _x, y + _y);

                if (a == 255)
                {
                    if (bSrcAlpha != 255)
                    {
                        // per-pixel alpha is not set, source constant alpha is set
                        r = ((r * bSrcAlpha + (255 - bSrcAlpha) * GetRValue(dstRGB)) / 255);
                        g = ((g * bSrcAlpha + (255 - bSrcAlpha) * GetGValue(dstRGB)) / 255);
                        b = ((b * bSrcAlpha + (255 - bSrcAlpha) * GetBValue(dstRGB)) / 255);
                    }
                }
                else
                {
                    if (bSrcAlpha != 255)
                    {
                        r = r * bSrcAlpha / 255;
                        g = g * bSrcAlpha / 255;
                        b = b * bSrcAlpha / 255;
                        a = a * bSrcAlpha / 255;
                    }

                    r = r + (255 - a) * GetRValue(dstRGB) / 255;
                    g = g + (255 - a) * GetGValue(dstRGB) / 255;
                    b = b + (255 - a) * GetBValue(dstRGB) / 255;
                }

                if (r > 255) r = 255;
                if (g > 255) g = 255;
                if (b > 255) b = 255;

//  warning 30022: Accessibility Violation: Calling a function which takes a COLORREF as an argument
//                 where the COLORREF argument is not the return value from GetSysColors Win32 API.
#pragma warning(push)
#pragma warning(disable:30022)
                SetPixel(hdc, x + _x, y + _y, RGB((BYTE)r, (BYTE)g, (BYTE)b));
#pragma warning(pop)
            }
        }
        
        if (bmih->biHeight > 0)
        {
            // bottom-up
            pdwARGB -= bmih->biWidth;
        }
        else
        {
            // top-down
            pdwARGB += bmih->biWidth;
        }
    }
    
    ret.cx = bmih->biWidth;
    ret.cy = m_nHeight;
#else
    //
    // alpha blend with GDI
    //

    if (!m_hBitmap && m_pData)
    {

        BITMAPINFOHEADER *bmih;
        BYTE *bits;
        
        bmih = (BITMAPINFOHEADER *)(m_pData + sizeof(BITMAPFILEHEADER));
        bits = m_pData + ((BITMAPFILEHEADER *)m_pData)->bfOffBits;
        
        m_hBitmap = CreateDIBitmap(
////////////////////    hdc,              // this doesn't work unless device color depth is 32.
                        g_pDataManager->GetRenderer()->Get32bitDC(),
                        bmih,
                        CBM_INIT,
                        bits,
                        (BITMAPINFO *)bmih,
                        DIB_RGB_COLORS);

        _ClearData();
    }

    //
    // Call GDI AlphaBlend
    // CImage::AlphaBlend didn't work. 
    //
    if (m_hBitmap)
    {
        HDC hMemDC = CreateCompatibleDC(hdc);
        
        if (hMemDC)
        {
            HBITMAP hbmpOld = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
            
            BLENDFUNCTION blendFunc;
            blendFunc.BlendOp = AC_SRC_OVER;
            blendFunc.BlendFlags = 0;
            blendFunc.SourceConstantAlpha = bSrcAlpha;
            blendFunc.AlphaFormat = AC_SRC_ALPHA;
            
            ::AlphaBlend(
                hdc,
                x,
                y,
                m_nWidth,
                m_nHeight,
                hMemDC,
                0,
                0,
                m_nWidth,
                m_nHeight,
                blendFunc);

            SelectObject(hMemDC, hbmpOld);
            
            ret.cx = m_nWidth;
            ret.cy = m_nHeight;
            
            DeleteDC(hMemDC);
        }
    }
#endif

    return ret;
}

